
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <memory>

#include <iostream>

#include "Blit.h"
#include "Camera.h"
#include "CustomMaterial.hpp"
#include "Geometry.h"
#include "Lights.hpp"
#include "Object.h"
#include "RenderContext.h"
#include "Shader.h"
#include "Texture.h"
#include "Window.h"

using namespace Umikaze;
using namespace Core;

#define TAA_ON 1

//借鉴于文刀秋二的低差异序列文章
double RadicalInverse(int Base, int i) {
  double Digit, Radical, Inverse;
  Digit = Radical = 1.0 / (double)Base;
  Inverse = 0.0;
  while (i) {
    // i余Base求出i在"Base"进制下的最低位的数
    // 乘以Digit将这个数镜像到小数点右边
    Inverse += Digit * (double)(i % Base);
    Digit *= Radical;

    // i除以Base即可求右一位的数
    i /= Base;
  }
  return Inverse;
}

// base最好是质数
double Halton(int Base, int Index) {
  // 直接用第Dimension个质数作为底数调用RadicalInverse即可
  return RadicalInverse(Base, Index);
}

glm::vec2 HaltonVector2(int Base1, int Base2, int Index) {
  return glm::vec2(Halton(Base1, Index), Halton(Base2, Index));
}

// TAA
int main() {

  int width = 1600;
  int height = 1200;
  auto &window = Window::instance();
  window->init(width, height);
  std::shared_ptr<Geometry> sphereGeometry =
      std::make_shared<SphereGeometry>(1.0, 64, 64);
  std::shared_ptr<Geometry> planeGeometry =
      std::make_shared<PlaneGeometry>(16.0f, 16.0f);

  std::shared_ptr<Geometry> cubeGeometry =
      std::make_shared<CubeGeometry>(2, 2, 2);

  auto material = std::make_shared<PbrMaterial>();
  material->setMetallic(0.5f);
  material->setRoughness(0.1f);

  auto mat = glm::lookAt(glm::vec3(10, 10, 10), glm::vec3(0, 0, 0),
                         glm::vec3(0, 1, 0));

  auto wife = std::make_shared<ImageTexture>("wife.jpg", true);
  material->_map = wife;
  material->_color = glm::vec3(1.0, 0.0, 0.0);

  glm::vec3 position(0, 0, 0);
  glm::vec3 rotation(0, 0, 0);
  glm::vec3 scale(1, 1, 1);

  auto cube = std::make_shared<Object>(cubeGeometry, material, position,
                                       rotation, scale);

  auto plane = std::make_shared<Object>(planeGeometry, material, position,
                                        rotation, scale);

  gDefaultCamera->_position = glm::vec3(0.0f, 0.0f, 3.0);

  std::vector<std::shared_ptr<Object>> objects;

  glm::vec3 cubePositions[] = {
      glm::vec3(0.0f, 0.0f, 0.0f),    glm::vec3(2.0f, 5.0f, -15.0f),
      glm::vec3(-1.5f, -2.2f, -2.5f), glm::vec3(-3.8f, -2.0f, -12.3f),
      glm::vec3(2.4f, -0.4f, -3.5f),  glm::vec3(-1.7f, 3.0f, -7.5f),
      glm::vec3(1.3f, -2.0f, -2.5f),  glm::vec3(1.5f, 2.0f, -2.5f),
      glm::vec3(1.5f, 0.2f, -1.5f),   glm::vec3(-1.3f, 1.0f, -1.5f)};

  for (auto i = 0; i < 10; ++i) {
    //暂时没有打算增加场景树结构
    auto object = std::make_shared<Object>(cubeGeometry, material,
                                           cubePositions[i], rotation, scale);
    objects.push_back(object);
  }

  std::shared_ptr<RenderContext> context = std::make_shared<RenderContext>();

  DirectionalLight light1;
  light1._colorAndIntensity = glm::vec4(1.f, 1.f, 1.f, 5.f);
  light1._direction = glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);

  Lights lights;
  lights.addDirectionalLight(light1);
  lights.setAmbient(glm::vec4(0.4));

  material->setLight(lights.getLightData());

  int32_t pixelWidth = Window::instance()->getPixelWidth();
  int32_t pixelHeight = Window::instance()->getPixelHeight();

  std::shared_ptr<RenderTexture> taaColor = std::make_shared<RenderTexture>(
      pixelWidth, pixelHeight, ColorFormat::RG16);

  std::shared_ptr<RenderTexture> taaDepth = std::make_shared<RenderTexture>(
      pixelWidth, pixelHeight, DepthStencilFormat::DEPTH16);

  std::shared_ptr<RenderTarget> taaTarget =
      std::make_shared<RenderTarget>(taaColor, taaDepth);

  std::shared_ptr<RenderTexture> colorTexture = std::make_shared<RenderTexture>(
      pixelWidth, pixelHeight, ColorFormat::RGBA8888);

  std::shared_ptr<RenderTexture> depthTexture = std::make_shared<RenderTexture>(
      pixelWidth, pixelHeight, DepthStencilFormat::DEPTH24);

  std::shared_ptr<RenderTarget> renderTarget =
      std::make_shared<RenderTarget>(colorTexture, depthTexture);

  std::shared_ptr<RenderTexture> accumulateTexture =
      std::make_shared<RenderTexture>(pixelWidth, pixelHeight,
                                      ColorFormat::RGBA8888);

  std::shared_ptr<RenderTexture> tempTexture = std::make_shared<RenderTexture>(
      pixelWidth, pixelHeight, ColorFormat::RGBA8888);

  std::unique_ptr<Shader> taaShader =
      std::make_unique<Shader>("TaaShader.vert", "TaaShader.frag");

  std::shared_ptr<CubeTexture> skyBox =
      std::make_shared<CubeTexture>("skybox/", ".jpg");

  gDefaultCamera->_near = 0.01f;

  uint32_t offsetIndex = 0;
  uint32_t accu = 0;

  glm::mat4 oldView;
  glm::mat4 oldProj;

  while (!window->shouldClose()) {
    window->startDraw();

    auto view = gDefaultCamera->getViewMatrix();
    auto proj = gDefaultCamera->getProjMatrix();
    auto pos = gDefaultCamera->getPosition();

    objects[0]->setRotation(90, 0, 0);

    if (accu % 1000 == 0) {
      objects[0]->setPosition(glm::vec3(0));
    } else {
      objects[0]->translate(glm::vec3(0.005, 0.0, 0.0));
      objects[0]->translate(glm::vec3(0.0, 0.005, 0.0));
    }

    context->setClearColor(0.5f, 0.2f, 0.0f, 0.0f);
    context->setClearBuffer(1);
    context->setDepthTest(true);
    context->setCullFace(false);
    context->setCullFaceMode(123);

#if TAA_ON
    if (accu != 0) {
      context->setCameraProperties(gDefaultCamera);
      context->setRenderTarget(taaTarget);
      context->drawMotionVector(objects, oldView, oldProj);
    }
    auto haltonVec = HaltonVector2(2, 3, offsetIndex) * 2.0f - 1.0f;
    auto pixelSize = glm::vec2(1 / pixelWidth, 1 / pixelHeight);
    auto uvOffset = haltonVec * pixelSize;
    auto jittedProj = glm::translate(proj, glm::vec3(uvOffset, 0));

    context->setViewAndProjectionMatrix(view, jittedProj);
#else
    context->setViewAndProjectionMatrix(view, proj);
#endif
    context->setRenderTarget(renderTarget);

    context->draw(objects);

    context->drawSkyBox(skyBox);

#if TAA_ON
    taaShader->setVec2("pixelSize", pixelSize);
    taaShader->setTexture("vectorMap", taaColor);
    taaShader->setTexture("accumulateMap", accumulateTexture);

    Blit::blitColor(colorTexture, tempTexture, taaShader);

    Blit::blitColor(tempTexture, accumulateTexture);

    Blit::blitToDefault(tempTexture);
#else
    Blit::blitToDefault(colorTexture);
#endif

    window->endDraw();

    offsetIndex++;
    offsetIndex %= 16;

    oldProj = proj;
    oldView = view;

    accu++;
  }

  return 1;
}